﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tao.DevIl;
using Tao.FreeGlut;
using Tao.OpenGl;

namespace OpenGl
{
    public class Initiator
    {
        private int displayMode;
        public int windowWidth { get; set;}
        public int windowHeigth { get; set; }
        public string windowTitle { get; set; }
        private bool GameMode;
        private string[] TexturesPath;
        private uint[] TexturesIDs;
        private float[] LookAt { get; set; }
        private float[] Ortho { get; set; }

        public delegate void MainLoopFunc();

        private MainLoopFunc MainLoop;

        private void setParamsPrivate(string windowTitile, int windowWidth, int windowHeight, bool GameMode = false,
                                      bool GLUT_RGBA = true, bool GLUT_DOUBLE = true, bool GLUT_DEPTH = false, string[] TexturesPath = null, float[] LookAt = null, float[] Ortho = null)
        {
            int GL_RGBA = GLUT_RGBA ? Glut.GLUT_RGBA : 0;
            int GL_Double = GLUT_DOUBLE ? Glut.GLUT_DOUBLE : 0;
            int GL_DEPTH = GLUT_DEPTH ? Glut.GLUT_DEPTH : 0;
            this.displayMode = GL_RGBA | GL_Double | GL_DEPTH;

            this.windowHeigth = windowHeight;
            this.windowWidth = windowWidth;
            this.windowTitle = windowTitile;
            this.GameMode = GameMode;
            this.TexturesPath = TexturesPath;
            if(LookAt == null) LookAt = new float[9]{0,0,0,1,0,0,0,1,0};
            this.LookAt = LookAt;
            if (Ortho == null) Ortho = new float[6] { 0.1f, 1.0f, 0.1f, 1.0f, -1.0f, 1.0f };
            this.Ortho = Ortho;
        }
        public Initiator(string windowTitile, int windowWidth, int windowHeight, bool GameMode = false,
                         bool GLUT_RGBA = true, bool GLUT_DOUBLE = true, bool GLUT_DEPTH = false, string[] TexturesPath = null, float[] LookAt = null, float[] Ortho = null)
        {
            setParamsPrivate(windowTitile, windowWidth, windowHeight, GameMode, GLUT_RGBA, GLUT_DOUBLE, GLUT_DEPTH, TexturesPath, LookAt, Ortho);
        }

        public Initiator()
        {
            this.displayMode = Glut.GLUT_RGBA | Glut.GLUT_DOUBLE;
            this.windowHeigth = 600;
            this.windowWidth = 800;
            this.windowTitle = "GAME";
            this.GameMode = false;
            LookAt = new float[9]{0,0,0,1,0,0,0,1,0};
        }


        #region Lights

        public void SetLight0(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT0);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, position);
        }

        public void SetLight1(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT1);
            Gl.glLightfv(Gl.GL_LIGHT1, Gl.GL_POSITION, position);
        }

        public void SetLight2(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT2);
            Gl.glLightfv(Gl.GL_LIGHT2, Gl.GL_POSITION, position);
        }

        public void SetLight3(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT3);
            Gl.glLightfv(Gl.GL_LIGHT3, Gl.GL_POSITION, position);
        }

        public void SetLight4(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT4);
            Gl.glLightfv(Gl.GL_LIGHT4, Gl.GL_POSITION, position);
        }

        public void SetLight5(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT5);
            Gl.glLightfv(Gl.GL_LIGHT5, Gl.GL_POSITION, position);
        }

        public void SetLight6(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT6);
            Gl.glLightfv(Gl.GL_LIGHT6, Gl.GL_POSITION, position);
        }

        public void SetLight7(float[] position)
        {
            Gl.glEnable(Gl.GL_LIGHT7);
            Gl.glLightfv(Gl.GL_LIGHT7, Gl.GL_POSITION, position);
        }
        #endregion


        public void Init()
        {
            Glut.glutInit();
            if (!GameMode)
            {
                Glut.glutInitWindowSize(windowWidth, windowHeigth);
                Glut.glutCreateWindow(windowTitle);
            }
            else
            {
                Glut.glutGameModeString(Convert.ToInt32(windowWidth)+"X"+Convert.ToInt32(windowHeigth)+":60");
            }
            Glut.glutInitDisplayMode(displayMode);
            Gl.glEnable(Gl.GL_LIGHTING);
            Gl.glLightModelf(Gl.GL_LIGHT_MODEL_TWO_SIDE, Gl.GL_TRUE);
            Gl.glEnable(Gl.GL_NORMALIZE);
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
            Gl.glOrtho(Ortho[0], Ortho[1], Ortho[2], Ortho[3], Ortho[4], Ortho[5]);
            Gl.glEnable(Gl.GL_DEPTH_TEST);
            LoadTextures();
            Glu.gluLookAt(LookAt[0], LookAt[1], LookAt[2], LookAt[3], LookAt[4], LookAt[5], LookAt[6], LookAt[7], LookAt[8]);
            Glut.glutReshapeFunc(Reshape);
            if (MainLoop != null)
            {
                Glut.glutDisplayFunc(MainLoopGo);
                Glut.glutMainLoop();
            }
                
        }

        public void LoadTextures()
        {
            Il.ilInit();
            int imageId;
            List<uint> texturesID = new List<uint>();
            if (TexturesPath != null)
            {
                foreach (var FileName in TexturesPath)
                {
                    Il.ilGenImages(1, out imageId);
                    Il.ilBindImage(imageId);
                    if (Il.ilLoadImage(FileName))
                    {
                        int width = Il.ilGetInteger(Il.IL_IMAGE_WIDTH);
                        int height = Il.ilGetInteger(Il.IL_IMAGE_HEIGHT);
                        int bitspp = Il.ilGetInteger(Il.IL_IMAGE_BITS_PER_PIXEL);
                        uint mGlTextureObject = 0;

                        switch (bitspp)
                        {
                            case 24:
                                mGlTextureObject = MakeGlTexture(Gl.GL_RGB, Il.ilGetData(), width, height);
                                break;
                            case 32:
                                mGlTextureObject = MakeGlTexture(Gl.GL_RGBA, Il.ilGetData(), width, height);
                                break;
                        }
                        Il.ilDeleteImages(1, ref imageId);
                        texturesID.Add(mGlTextureObject);
                    }
                }
                TexturesIDs = texturesID.ToArray();
            }
        }

        private static uint MakeGlTexture(int Format, IntPtr pixels, int w, int h)
        {
            uint texObject;
            Gl.glGenTextures(1, out texObject);
            Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT, 1);
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, texObject);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
            Gl.glTexEnvf(Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_REPLACE);
            switch (Format)
            {
                case Gl.GL_RGB:
                    Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB, w, h, 0, Gl.GL_RGB, Gl.GL_UNSIGNED_BYTE, pixels);
                    break;
 
                case Gl.GL_RGBA:
                    Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, w, h, 0, Gl.GL_RGBA, Gl.GL_UNSIGNED_BYTE, pixels);
                    break;
 
            }
            return texObject;
        }

        public void CloseWindow()
        {
            Glut.glutHideWindow();
        }

        public void Reset()
        {
            CloseWindow();
            Init();
        }

        public void SetParams(string windowTitile, int windowWidth, int windowHeight, bool GameMode = false,
                         bool GLUT_RGBA = true, bool GLUT_DOUBLE = true, bool GLUT_DEPTH = false, string[] TexturesPath = null, float[] LookAt = null, float[] Ortho = null)
        {
            if(LookAt == null) LookAt = new float[9]{0,0,0,1,0,0,0,1,0};
            if (Ortho == null) Ortho = new float[6] { 0.1f, 1.0f, 0.1f, 1.0f, -1.0f, 1.0f };
            setParamsPrivate(windowTitile, windowWidth, windowHeight, GameMode, GLUT_RGBA, GLUT_DOUBLE, GLUT_DEPTH, TexturesPath, LookAt, Ortho);
        }
        public void setCamera(float[] coords)
        {
            this.LookAt = coords;
        }

        private void Reshape(int width, int height)
        {
            Gl.glViewport(0, 0, width, height);
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
            Gl.glOrtho(Ortho[0], Ortho[1], Ortho[2], Ortho[3], Ortho[4], Ortho[5]);
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
        }

        public void SetOrtho(float[] Ortho)
        {
            if (Ortho != null)
            {
                this.Ortho = Ortho;
            }
        }

        public void SetMainLoopFunc(MainLoopFunc func)
        {
            MainLoop = func;
        }

        private void MainLoopGo()
        {
            MainLoop();
        }
    }
}
